import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

import javax.swing.*;

public class FTPClient {
    private JFrame frame;
    private JTextField hostField;
    private JTextField portField;
    private JTextField userField;
    private JPasswordField passField;
    private JTextField fileField;
    private JList<String> fileList;
    private JProgressBar progressBar;
    private Socket controlSocket;
    private BufferedReader reader;
    private BufferedWriter writer;
    private String currentPath = "/"; // 当前目录路径
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            try {
                FTPClient window = new FTPClient();
                window.frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    public FTPClient() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame();
        frame.setBounds(0, 0, 1580, 980);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new CardLayout());

        // Background Panel
        JPanel loginPanel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                ImageIcon icon = new ImageIcon("C:\\Users\\Administrator\\OneDrive\\桌面\\ftp.png"); // Add your image path here
                Image img = icon.getImage();
                g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
            }
        };
        loginPanel.setLayout(new GridBagLayout());
        frame.getContentPane().add(loginPanel, "Login");

        JPanel loginFields = new JPanel();
        loginFields.setLayout(new BoxLayout(loginFields, BoxLayout.Y_AXIS));
        loginFields.setOpaque(false);

        hostField = new JTextField(15);
        portField = new JTextField(15);
        userField = new JTextField(15);
        passField = new JPasswordField(15);

        loginFields.add(createLabeledField("Host IP", hostField));
        loginFields.add(createLabeledField("Port", portField));
        loginFields.add(createLabeledField("Username", userField));
        loginFields.add(createLabeledField("Password", passField));

        RoundedButton connectButton = new RoundedButton("Connect");
        connectButton.setPreferredSize(new Dimension(450, 38));
        connectButton.setBorderPainted(false); 
        connectButton.setBorder(BorderFactory.createRaisedBevelBorder()); 
        connectButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        connectButton.addActionListener(e -> connect());
        loginFields.add(Box.createVerticalStrut(12)); // Add space between fields and button
        loginFields.add(connectButton);

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.CENTER;
        loginPanel.add(loginFields, gbc);

        JPanel mainPanel = new JPanel(new BorderLayout());
        frame.getContentPane().add(mainPanel, "Main");

        fileField = new JTextField();
        mainPanel.add(fileField, BorderLayout.NORTH);

        fileList = new JList<>();
        mainPanel.add(new JScrollPane(fileList), BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel();
        mainPanel.add(buttonPanel, BorderLayout.SOUTH);

        RoundedButton uploadButton = new RoundedButton("Upload");
        uploadButton.setPreferredSize(new Dimension(200, 30));
        uploadButton.setBorderPainted(false); 
        uploadButton.setBorder(BorderFactory.createRaisedBevelBorder()); 
        uploadButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        uploadButton.addActionListener(e -> uploadFile());
        buttonPanel.add(uploadButton);

        RoundedButton downloadButton = new RoundedButton("Download");
        downloadButton.setPreferredSize(new Dimension(200, 30));
        downloadButton.setBorderPainted(false); 
        downloadButton.setBorder(BorderFactory.createRaisedBevelBorder()); 
        downloadButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        downloadButton.addActionListener(e -> downloadFile());
        buttonPanel.add(downloadButton);

        RoundedButton deleteButton = new RoundedButton("Delete");
        deleteButton.setPreferredSize(new Dimension(200, 30));
        deleteButton.setBorderPainted(false);
        deleteButton.setBorder(BorderFactory.createRaisedBevelBorder());
        deleteButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        deleteButton.addActionListener(e -> deleteFile());
        buttonPanel.add(deleteButton);

        RoundedButton logoutButton = new RoundedButton("Logout");
        logoutButton.setPreferredSize(new Dimension(200, 30));
        logoutButton.setBorderPainted(false); 
        logoutButton.setBorder(BorderFactory.createRaisedBevelBorder()); 
        logoutButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        logoutButton.addActionListener(e -> logout());
        buttonPanel.add(logoutButton);


        progressBar = new JProgressBar(0, 100);
        progressBar.setStringPainted(true);
        mainPanel.add(progressBar, BorderLayout.NORTH);

        fileList.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) { // 双击进入目录
                    String selectedItem = fileList.getSelectedValue();
                    if (selectedItem != null) {
                        // 处理目录点击
                        if (selectedItem.endsWith("/")) { // 判断是否为目录
                            currentPath = currentPath + selectedItem; // 更新当前路径
                            listFiles(); // 刷新文件列表
                        }
                    }
                }
            }
        });
    }

    private JPanel createLabeledField(String labelText, JComponent field) {
        JPanel panel = new JPanel();
        panel.setOpaque(false);
        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
    
        // Create and set the label with larger font
        JLabel label = new JLabel(labelText);
        label.setForeground(Color.WHITE); // Set label text color
        label.setFont(new Font("Arial", Font.PLAIN, 18)); // Set font size for the label
        label.setPreferredSize(new Dimension(120, 30)); // Ensure label width is fixed
        
        // Adjust the field size and font
        field.setFont(new Font("Arial", Font.PLAIN, 18)); // Set font size for the text field
        field.setPreferredSize(new Dimension(200, 30)); // Set preferred size for the text field
    
        panel.add(label);
        panel.add(Box.createHorizontalStrut(10)); // Add space between label and field
        panel.add(field);
        panel.setAlignmentX(Component.CENTER_ALIGNMENT);
        panel.setMaximumSize(new Dimension(500, 50)); // Set maximum size for consistent alignment
    
        return panel;
    }

    private void connect() {
        String host = hostField.getText();
        String port = portField.getText();
        String user = new String(userField.getText());//userField.getText();
        String pass = new String(passField.getPassword());

        if (host.isEmpty() || port.isEmpty() || user.isEmpty() || pass.isEmpty()) {
            JOptionPane.showMessageDialog(frame, "All fields are required");
            return;
        }

        try {
            controlSocket = new Socket(host, Integer.parseInt(port));
            reader = new BufferedReader(new InputStreamReader(controlSocket.getInputStream()));
            writer = new BufferedWriter(new OutputStreamWriter(controlSocket.getOutputStream()));

            if (!readResponse().startsWith("220")) {
                throw new IOException("Failed to connect to the server");
            }

            sendCommand("USER " + user);
            if (!readResponse().startsWith("220")) {
                throw new IOException("Invalid username");
            }
            if(!readResponse().startsWith("331")){
                throw new IOException("No reply in password");
            }
            sendCommand("PASS " + pass);
            if (!readResponse().startsWith("230")) {
                throw new IOException("Invalid password");
            }

            JOptionPane.showMessageDialog(frame, "Connected to FTP server");
            CardLayout cl = (CardLayout) (frame.getContentPane().getLayout());
            cl.show(frame.getContentPane(), "Main");
            listFiles();
        } catch (IOException e) {
            JOptionPane.showMessageDialog(frame, "Connection error: " + e.getMessage());
        }
    }


    private void sendCommand(String command) throws IOException {
        System.out.println("Command: " + command);
        writer.write(command + "\r\n");
        writer.flush();
    }
    
    private String readResponse() throws IOException {
        String response = reader.readLine();
        System.out.println("Response: " + response);
        return response;
    }


    private void uploadFile() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setCurrentDirectory(new File(currentPath)); // Set starting directory
        fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); // Allow selection of files and directories

        int returnValue = fileChooser.showOpenDialog(frame);
        if (returnValue == JFileChooser.APPROVE_OPTION) {
            File selectedFile = fileChooser.getSelectedFile();
            if (selectedFile.isDirectory()) {
                // Handle directory upload
                uploadDirectory(selectedFile);
            } else {
                // Handle file upload with resume capability
                try {
                    long uploadedBytes = getUploadedBytes(selectedFile.getName());
                    uploadSingleFile(selectedFile, uploadedBytes);
                    listFiles();
                } catch (IOException e) {
                    JOptionPane.showMessageDialog(frame, "Upload error: " + e.getMessage());
                }
            }
        }
    }

    private void uploadDirectory(File directory) {
        try {
            sendCommand("MKD " + directory.getName());
            readResponse();

            sendCommand("CWD " + directory.getName());
            readResponse();

            for (File file : directory.listFiles()) {
                if (file.isDirectory()) {
                    uploadDirectory(file);
                } else {
                    long uploadedBytes = getUploadedBytes(file.getName());
                    uploadSingleFile(file, uploadedBytes);
                }
            }

            sendCommand("CWD ..");
            readResponse();
        } catch (IOException e) {
            JOptionPane.showMessageDialog(frame, "Upload error: " + e.getMessage());
        }
    }

    private void uploadSingleFile(File file, long offset) throws IOException {
        try (RandomAccessFile fileInput = new RandomAccessFile(file, "r")) {
            sendCommand("TYPE I");
            readResponse();

            sendCommand("PASV");
            String response = readResponse();
            String[] parts = response.split("[()]")[1].split(",");
            String ip = parts[0] + "." + parts[1] + "." + parts[2] + "." + parts[3];
            int port = Integer.parseInt(parts[4]) * 256 + Integer.parseInt(parts[5]);

            fileInput.seek(offset);

            sendCommand("REST " + offset);
            readResponse();

            sendCommand("STOR " + file.getName());
            readResponse();

            Socket dataSocket = new Socket(ip, port);
            BufferedOutputStream dataOut = new BufferedOutputStream(dataSocket.getOutputStream());

            progressBar.setValue((int) offset);
            progressBar.setMaximum((int) file.length());

            byte[] buffer = new byte[4096];
            int bytesRead;
            long totalBytesRead = offset;
            while ((bytesRead = fileInput.read(buffer)) != -1) {
                dataOut.write(buffer, 0, bytesRead);
                totalBytesRead += bytesRead;
                progressBar.setValue((int) totalBytesRead);
            }

            dataOut.close();
            dataSocket.close();
            readResponse();
            //JOptionPane.showMessageDialog(frame, "File uploaded successfully");
        }
    }

    private long getUploadedBytes(String fileName) throws IOException {
        sendCommand("SIZE " + fileName);
        String response = readResponse();
        if (response.startsWith("213")) {
            return Long.parseLong(response.split(" ")[1]);
        }
        return 0;
    }
    
    private void deleteFile() {
        String fileName = fileList.getSelectedValue();
        if (fileName == null) {
            JOptionPane.showMessageDialog(frame, "No file selected for deletion.");
            return;
        }
    
        int response = JOptionPane.showConfirmDialog(frame, "Are you sure you want to delete " + fileName + "?", "Confirm Deletion", JOptionPane.YES_NO_OPTION);
        if (response == JOptionPane.YES_OPTION) {
            try {
                sendCommand("DELE " +currentPath+ fileName);
                String serverResponse = readResponse();
                if (serverResponse.startsWith("250")) {
                    JOptionPane.showMessageDialog(frame, "File deleted successfully.");
                    listFiles(); // Refresh the file list
                } else {
                    JOptionPane.showMessageDialog(frame, "Error deleting file: " + serverResponse);
                }
            } catch (IOException e) {
                JOptionPane.showMessageDialog(frame, "Error deleting file: " + e.getMessage());
            }
        }
    }
    

    private void ensureLoggedIn() throws IOException {
        sendCommand("USER " + userField.getText());
        String userResponse = readResponse();
        if (!userResponse.startsWith("230") && !userResponse.startsWith("331")) {
            throw new IOException("User command failed: " + userResponse);
        }
        sendCommand("PASS " + new String(passField.getPassword()));
        String passResponse = readResponse();
        if (passResponse.startsWith("331")) {
            sendCommand("PASS " + new String(passField.getPassword()));
        }
        if (!passResponse.startsWith("230")) {
            throw new IOException("Login failed: " + passResponse);
        }
    }
    
    private void downloadFile() {
        String fileName = fileList.getSelectedValue();
        if (isDirectory(fileName)) {
            JFileChooser dirChooser = new JFileChooser();
            dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            int returnVal = dirChooser.showSaveDialog(frame);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File saveDir = dirChooser.getSelectedFile();
                try {
                    downloadDirectory(fileName, saveDir);
                    JOptionPane.showMessageDialog(frame, "目录下载成功");
                } catch (IOException e) {
                    e.printStackTrace();
                    JOptionPane.showMessageDialog(frame, "下载错误: " + e.getMessage());
                }
            }
        } else {
            downloadSingleFile(fileName);
        }
    }
    
    private void downloadDirectory(String remoteDir, File localDir) throws IOException {
        File dirToCreate = new File(localDir, remoteDir);
        dirToCreate.mkdirs(); // 创建本地目录
        ensureLoggedIn();
        sendCommand("CWD " + remoteDir); // 切换到远程目录
        readResponse();
        
        Socket dataSocket =  initiatePassiveMode();
        
        sendCommand("LIST");
        readResponse();
    
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(dataSocket.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                String name = parseFileName(line);
                if (line.startsWith("d") && !(name.equals(".") || name.equals(".."))) {
                    downloadDirectory(remoteDir + name, new File(localDir, name));
                } else if (!line.startsWith("d")) {
                    downloadSingleFile(remoteDir + name, new File(localDir, name));
                }
            }
        }
        dataSocket.close();
        sendCommand("CWD .."); // 返回上级目录
        readResponse();
    }
    
    private String parseFileName(String line) {
        String[] parts = line.split(" ");
        return parts[parts.length - 1];
    }
    
    private void downloadSingleFile(String remoteFile) {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setSelectedFile(new File(remoteFile));
        int returnVal = fileChooser.showSaveDialog(frame);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File saveFile = fileChooser.getSelectedFile();
            downloadSingleFile(remoteFile, saveFile);
        }
    }
    
    private void downloadSingleFile(String remoteFile, File saveFile) {
        long downloadedSize = 0;
    
        if (saveFile.exists()) {
            downloadedSize = saveFile.length();
        }
    
        try {
            ensureLoggedIn();
            long fileSize = getFileSize(remoteFile); // 获取文件大小
    
            sendCommand("REST " + downloadedSize);
            String response = readResponse();
            if (!response.startsWith("350")) {
                JOptionPane.showMessageDialog(frame, "服务器拒绝 REST 命令: " + response);
                return;
            }
    
            Socket dataSocket = initiatePassiveMode(); // 初始化被动模式
    
            sendCommand("RETR " +currentPath +remoteFile); // 下载文件
            String serverResponse = readResponse();
    
            if (serverResponse.startsWith("150")) {
                progressBar.setValue(0);
                progressBar.setMaximum((int) fileSize);
    
                try (RandomAccessFile raf = new RandomAccessFile(saveFile, "rw");
                     InputStream is = dataSocket.getInputStream()) {
    
                    raf.seek(downloadedSize);
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    long totalBytesRead = downloadedSize;
                    while ((bytesRead = is.read(buffer)) != -1) {
                        raf.write(buffer, 0, bytesRead);
                        totalBytesRead += bytesRead;
                        progressBar.setValue((int) totalBytesRead);
                    }
                } finally {
                    dataSocket.close();
                }
                readResponse();
                JOptionPane.showMessageDialog(frame, "文件下载成功");
            } else {
                JOptionPane.showMessageDialog(frame, "文件传输错误: " + serverResponse);
            }
        } catch (IOException e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(frame, "下载错误: " + e.getMessage());
        }
    }
    
    private Socket initiatePassiveMode() throws IOException {
        while (true) {
            sendCommand("PASV");
            String response = readResponse();
            if (response.startsWith("227")) {
                return createDataSocket(response);
            } else {
                System.out.println("Invalid PASV response: " + response + ". Retrying...");
            }
        }
    }

    private boolean isDirectory(String fileName) {
        try {
            ensureLoggedIn();
            sendCommand("CWD " + fileName);
            String response = readResponse();
            if (response.startsWith("250")) {
                sendCommand("CWD ..");
                readResponse();
                return true;
            }
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
    
    private Socket createDataSocket(String response) throws IOException {
        if (response.contains("(") && response.contains(")")) {
            String[] parts = response.split("[()]")[1].split(",");
            if (parts.length == 6) {
                String ip = parts[0] + "." + parts[1] + "." + parts[2] + "." + parts[3];
                int port = (Integer.parseInt(parts[4]) << 8) + Integer.parseInt(parts[5]);
                return new Socket(ip, port);
            } else {
                throw new IOException("Unexpected data format in PASV response.");
            }
        } else {
            throw new IOException("Invalid PASV response: " + response);
        }
    }
    
    private long getFileSize(String fileName) throws IOException {
        sendCommand("SIZE " +currentPath+ fileName);
        String response = readResponse();
        if (response.startsWith("213")) {
            return Long.parseLong(response.split(" ")[1]);
        } else {
            throw new IOException("Failed to get file size: " + response);
        }
    }
    
    private void listFiles() {
    try {
        sendCommand("PASV");
        Socket dataSocket = createDataSocket(readResponse());

        sendCommand("LIST " + currentPath);
        readResponse();

        BufferedReader dataReader = new BufferedReader(new InputStreamReader(dataSocket.getInputStream(), StandardCharsets.UTF_8));
        DefaultListModel<String> model = new DefaultListModel<>();
        String line;
        while ((line = dataReader.readLine()) != null) {
            System.out.println(line);
            String fileName = line.split("\\s+")[line.split("\\s+").length - 1];
            if (line.startsWith("d")) {
                model.addElement(fileName + "/");
            } else {
                model.addElement(fileName);
            }
        }
        fileList.setModel(model);
        dataSocket.close();
        readResponse();
    } catch (IOException e) {
        JOptionPane.showMessageDialog(frame, "File listing error: " + e.getMessage());
    }
}

//    private void navigateUp() {
//     // 如果当前路径为根目录，则不做任何操作
//     if (currentPath.equals("/")) {
//         JOptionPane.showMessageDialog(frame, "Already at the root directory.");
//         return;
//     }else{
//         int lastSlashIndex = currentPath.lastIndexOf('/');
//         int secondIndex = currentPath.lastIndexOf("/",lastSlashIndex - 1);
//         // 如果当前路径以斜杠结尾，调整斜杠位置
//         if (secondIndex <= 0) {
//             currentPath = "/" ;
//         }else if (secondIndex > 1) {
//             currentPath = currentPath.substring(0, secondIndex + 1);
//             if (currentPath.isEmpty()) {
//                 currentPath = "/"; // Ensure root directory path is '/'
//             }
//         }
//         listFiles();
//         return;
//     }
// }

    

    private void logout() {
        try {
            if (controlSocket != null && !controlSocket.isClosed()) {
                sendCommand("QUIT");
                reader.close();
                writer.close();
                controlSocket.close();
                JOptionPane.showMessageDialog(frame, "Disconnected from FTP server");
            }
        } catch (IOException e) {
            JOptionPane.showMessageDialog(frame, "Error during logout: " + e.getMessage());
        }
        CardLayout cl = (CardLayout) (frame.getContentPane().getLayout());
        cl.show(frame.getContentPane(), "Login");
    }
    public class RoundedButton extends JButton {
        private static final int ARC_WIDTH = 20; // 圆角宽度
        private static final int ARC_HEIGHT = 20; // 圆角高度
        private boolean isMouseOver = false;
        private boolean isPressed = false;
    
        public RoundedButton(String text) {
            super(text);
            setFocusable(false);
            setContentAreaFilled(false); // 移除默认背景
            setBorderPainted(false); // 移除默认边框
            setOpaque(false); // 使按钮透明
            setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); // 设置手形光标
            setBackground(new Color(173, 216, 230)); // 淡蓝色
    
            // 添加鼠标事件监听器
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    isMouseOver = true;
                    repaint(); // 重新绘制按钮以显示悬停效果
                }
    
                @Override
                public void mouseExited(MouseEvent e) {
                    isMouseOver = false;
                    repaint(); // 重新绘制按钮以恢复原样
                }
            });
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g.create();
            try {
                // 绘制圆角矩形
                Color bg = getBackground();
                if(isPressed){
                    //
                    g2d.setColor(bg.darker()); // 使背景颜色变暗
                }
                else if (isMouseOver) {
                    // 鼠标悬停时的背景颜色
                    g2d.setColor(bg.brighter()); // 使背景颜色变亮
                } else {
                    // 普通状态的背景颜色
                    g2d.setColor(bg);
                }
                /*if (!isPressed) {
                    g2d.setColor(Color.BLUE); // 蓝色轮廓
                    g2d.setStroke(new BasicStroke(2)); // 设置边框宽度
                    g2d.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, ARC_WIDTH, ARC_HEIGHT);
                }*/
                g2d.fillRoundRect(0, 0, getWidth(), getHeight(), ARC_WIDTH, ARC_HEIGHT);
    
                // 绘制按钮文字
                super.paintComponent(g);
            } finally {
                g2d.dispose();
            }
        }
    }

}
